const isFunction = (variable) => typeof variable === "function";
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECT = "REJECT";

class MyPromise {
  constructor(handle) {
    if (!isFunction(handle)) {
      throw new Error("MyPromise must accept a function as a parameter");
    }
    this._status = PENDING;
    this._value = undefined;
    this._fulfilledQueues = [];
    this._rejectedQueues = [];
    try {
      handle(this._resolve.bind(this), this._reject.bind(this));
    } catch (err) {
      this._reject(err);
    }
  }

  _resolve(val) {
    const run = () => {
      if (this._status !== PENDING) {
        return;
      }
      this._status = FULFILLED;
      const runFulfilled = (value) => {
        let cb;
        while ((cb = this._fulfilledQueues.shift())) {
          cb(value);
        }
      };
      const runRejected = (err) => {
        let cb;
        while ((cb = this._rejectedQueues.shift())) {
          cb(err);
        }
      };
      if (val instanceof MyPromise) {
        val.then(
          (value) => {
            this._value = value;
            runFulfilled(value);
          },
          (err) => {
            this._value = err;
            runRejected(err);
          }
        );
      } else {
        this._value = val;
        runFulfilled(val);
      }
    };
    setTimeout(run, 0);
  }

  _reject(err) {
    const run = () => {
      if (this._status !== PENDING) {
        return;
      }
      this._status = REJECT;
      this._value = err;
      let cb;
      while ((cb = this._rejectedQueues.shift())) {
        cb(err);
      }
    };
    setTimeout(run, 0);
  }

  then(onFulfilled, onRejected) {
    const { _value, _status } = this;
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      let fulfilled = (value) => {
        try {
          // 当参数不是函数时，就会跳过，把返回值传到下一个then
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value);
          } else {
            let res = onFulfilled(value);
            if (res instanceof MyPromise) {
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          onRejectedNext(err);
        }
      };
      let rejected = (error) => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error);
          } else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          onRejectedNext(err);
        }
      };
      switch (_status) {
        case PENDING:
          this._fulfilledQueues.push(fulfilled);
          this._rejectedQueues.push(rejected);
          break;
        case FULFILLED:
          fulfilled(_value);
          break;
        case REJECTED:
          rejected(_value);
          break;
      }
    });
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve) => resolve(value));
  }

  static reject(err) {
    if (err instanceof MyPromise) return err;
    return new MyPromise((resolve, reject) => {
      reject(err);
    });
  }

  static all(list) {
    return new MyPromise((resolve, reject) => {
      let values = [];
      let count = [];
      for (let [i, p] of list.entries()) {
        this.resolve(p).then(
          (res) => {
            values[i] = res;
            count++;
            if (count === list.lenght) resolve(values);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }

  static rece(list) {
    return new MyPromise((resolve, reject) => {
      for (let p of list) {
        this.resolve(p).then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
}

module.exports = MyPromise;
